﻿using System;
using System.Collections.Generic;
using System.Configuration.Provider;
using System.Net.Mail;

namespace Altairis.MailToolkit {

    public abstract class MailingListProvider : ProviderBase {
        protected const string DEFAULT_SUBSCRIPTION_VERIFICATION_TEMPLATE = "ListSubscriptionVerification";
        protected const string DEFAULT_REMOVAL_VERIFICATION_TEMPLATE = "ListRemovalVerification";
        protected const string DEFAULT_MESSAGE_TEMPLATE = "ListMessage";

        // Configuration

        public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) {
            // Initialize provider base
            base.Initialize(name, config);

            // Initialize general provider properties
            this.SubscriptionVerificationTemplate = config.GetConfigValue("subscriptionVerificationTemplate", DEFAULT_SUBSCRIPTION_VERIFICATION_TEMPLATE);
            this.RemovalVerificationTemplate = config.GetConfigValue("removalVerificationTemplate", DEFAULT_REMOVAL_VERIFICATION_TEMPLATE);
            this.MessageTemplate = config.GetConfigValue("messageTemplate", DEFAULT_MESSAGE_TEMPLATE);
        }

        /// <summary>
        /// Gets or sets the name of subscription verification template.
        /// </summary>
        /// <value>
        /// The name of subscription verification template.
        /// </value>
        /// <remarks>
        /// The template may contain the following placeholders:
        /// <c>{0}</c> - e-mail address (mandatory),
        /// <c>{1}</c> - generated confirmation code (mandatory),
        /// <c>{2}</c> - list name,
        /// <c>{3}</c> - list description
        /// </remarks>
        public string SubscriptionVerificationTemplate { get; set; }

        /// <summary>
        /// Gets or sets the name of removal verification template.
        /// </summary>
        /// <value>
        /// The name of removal verification template.
        /// </value>
        /// <remarks>
        /// The template may contain the following placeholders:
        /// <c>{0}</c> - e-mail address (mandatory),
        /// <c>{1}</c> - generated confirmation code (mandatory),
        /// <c>{2}</c> - list name
        /// <c>{3}</c> - list description
        /// </remarks>
        public string RemovalVerificationTemplate { get; set; }

        /// <summary>
        /// Gets or sets the name of message template.
        /// </summary>
        /// <value>
        /// The name of message template.
        /// </value>
        /// <remarks>
        /// The template must contain the following placeholders:
        /// <c>{0}</c> - message subject,
        /// <c>{1}</c> - message body
        /// <c>{2}</c> - list name
        /// <c>{3}</c> - list description
        /// </remarks>
        public string MessageTemplate { get; set; }

        // Subscription

        /// <summary>
        /// Sends message to request confirmation of subscription of given e-mail address to the mailing list.
        /// </summary>
        /// <param name="emailAddress">The e-mail address to be subscribed.</param>
        /// <returns>Returns <c>false</c>, when address is already member of the list, <c>true</c> otherwise.</returns>
        public virtual bool Subscribe(MailAddress emailAddress, params object[] additionalArgs) {
            // Validate arguments
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");
            if (this.IsMember(emailAddress)) return false;

            // Add formatting arguments
            object[] args;
            if (additionalArgs == null || additionalArgs.Length == 0) {
                args = new object[4];
            }
            else {
                args = new object[additionalArgs.Length + 4];
                Array.Copy(additionalArgs, 0, args, 4, additionalArgs.Length);
            }
            args[0] = emailAddress.Address.ToLower();         // 0
            args[1] = this.GetVerificationCode(emailAddress); // 1
            args[2] = this.Name.ToLower();                    // 2
            args[3] = this.Description;                       // 3

            // Send confirmation request
            Mailer.SendTemplatedMessage(emailAddress, this.SubscriptionVerificationTemplate, args);
            return true;
        }

        /// <summary>
        /// Subscribes the e-mail address to mailing list.
        /// </summary>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <param name="code">The confirmation code.</param>
        /// <returns>Returns <c>true</c> when subscription succeeded or <c>false</c> on invalid confirmation code or when e-mail is already in list.</returns>
        public virtual bool SubscribeConfirm(MailAddress emailAddress, string code) {
            // Validate arguments
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");
            if (code == null) throw new ArgumentNullException("code");
            if (string.IsNullOrWhiteSpace(code)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "code");

            // Verify code
            if (!this.CheckVerificationCode(emailAddress, code)) return false;  // Invalid code

            // Subscribe
            return this.SubscribeForce(emailAddress);
        }

        /// <summary>
        /// Subscribes the e-mail address to mailing list, without confirmation.
        /// </summary>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <returns>Returns <c>true</c> when subscription succeeded or <c>false</c> when e-mail is already in list.</returns>
        public abstract bool SubscribeForce(MailAddress emailAddress);

        // Removal

        /// <summary>
        /// Sends message to request confirmation of removal of given e-mail address from the mailing list.
        /// </summary>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <returns>Returns <c>false</c>, when address is not member of the list, <c>true</c> otherwise.</returns>
        public virtual bool Remove(MailAddress emailAddress, params object[] additionalArgs) {
            // Validate arguments
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");
            if (!this.IsMember(emailAddress)) return false;

            // Add formatting arguments
            object[] args;
            if (additionalArgs == null || additionalArgs.Length == 0) {
                args = new object[4];
            }
            else {
                args = new object[additionalArgs.Length + 4];
                Array.Copy(additionalArgs, 0, args, 4, additionalArgs.Length);
            }
            args[0] = emailAddress.Address.ToLower();         // 0
            args[1] = this.GetVerificationCode(emailAddress); // 1
            args[2] = this.Name.ToLower();                    // 2
            args[3] = this.Description;                       // 3

            // Send confirmation request
            Mailer.SendTemplatedMessage(emailAddress, this.RemovalVerificationTemplate, args);
            return true;
        }

        /// <summary>
        /// Removes the e-mail address from mailing list.
        /// </summary>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <param name="code">The confirmation code.</param>
        /// <returns>Returns <c>true</c> when removal succeeded or <c>false</c> on invalid confirmation code or when e-mail is not in list.</returns>
        public virtual bool RemoveConfirm(MailAddress emailAddress, string code) {
            // Validate arguments
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");
            if (code == null) throw new ArgumentNullException("code");
            if (string.IsNullOrWhiteSpace(code)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "code");

            // Verify code
            if (!this.CheckVerificationCode(emailAddress, code)) return false;  // Invalid code

            // Subscribe
            return this.RemoveForce(emailAddress);
        }

        /// <summary>
        /// Removes the e-mail address from mailing list, without confirmation.
        /// </summary>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <returns>Returns <c>true</c> when removal succeeded or <c>false</c> when e-mail is not in list.</returns>
        public abstract bool RemoveForce(MailAddress emailAddress);

        // Other

        /// <summary>
        /// Determines whether the specified email address is member of this list.
        /// </summary>
        /// <param name="emailAddress">The email address.</param>
        /// <returns>
        ///   <c>true</c> if the specified email address is member; otherwise, <c>false</c>.
        /// </returns>
        public abstract bool IsMember(MailAddress emailAddress);

        /// <summary>
        /// Gets the number of list members.
        /// </summary>
        /// <returns>Number of list members.</returns>
        public abstract int GetMemberCount();

        /// <summary>
        /// Gets the list of member e-mail addresses.
        /// </summary>
        /// <returns>List of member e-mail addresses.</returns>
        public abstract IEnumerable<string> GetMembers();

        // Helper methods to be overriden in inherited classes

        /// <summary>
        /// Computes the verification code for given e-mail address and list name.
        /// </summary>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <returns>URL-safe Base64-encoded verification code.</returns>
        protected virtual string GetVerificationCode(MailAddress emailAddress) {
            // Validate arguments
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");

            // Compute HMAC
            using (var mac = new System.Security.Cryptography.HMACSHA1()) {
                mac.Key = MailingListManager.VerificationKey;
                var emailData = System.Text.Encoding.UTF8.GetBytes(this.Name.ToLower() + ":" + emailAddress.Address.ToLower());
                var codeData = mac.ComputeHash(emailData);
                var codeString = System.Convert.ToBase64String(codeData);
                codeString = codeString.Replace('+', '-').Replace('/', '_').TrimEnd('=');
                return codeString;
            }
        }

        /// <summary>
        /// Checks the verification code for given e-mail address and list name.
        /// </summary>
        /// <param name="emailAddress">The e-mail address.</param>
        /// <param name="code">The verification code.</param>
        /// <returns>Returns <c>true</c> if the code is correct, <c>false</c> otherwise.</returns>
        protected virtual bool CheckVerificationCode(MailAddress emailAddress, string code) {
            // Validate arguments
            if (emailAddress == null) throw new ArgumentNullException("emailAddress");
            if (code == null) throw new ArgumentNullException("code");
            if (string.IsNullOrWhiteSpace(code)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "code");

            // Check verification code
            return code.Equals(this.GetVerificationCode(emailAddress), StringComparison.Ordinal);
        }
    }
}